/*
 * adpt.cpp
 *
 */
#include <sstream>

#include "../middleware/CommunicationRobot16.h"
#include "../middleware/CommunicationAdpt.h"
#include "../middleware/CommunicationDespacho.h"
#include "../application/Adpt.h"
#include "../logger/logger.h"

int main(int argv, char **agrv)
{

	CLogger::create("Almacen productos terminados");
	CLogger::setLevel(LogLevelEnum::TRACE);

	CLogger::log("Almacen productos terminados started", LogLevelEnum::INFO);

	std::string hostName = "adpt_pc";
	std::stringstream ss;
	application::Adpt almacen;
	middleware::CommunicationAdpt communicationAdpt(hostName);
	middleware::CommunicationRobot16 communicationRobot16;
	middleware::CommunicationDespacho communicationDespacho(hostName);

	while(true)
	{
		middleware::CommunicationAdpt::AdptRequestType reqType;
		ProductoCantidad reqProductosCantidades;
		OrdenCompraHeader reqOrdenCompra;
		int reqCantidadMinima;
		Producto reqProducto;

		communicationAdpt.getAdptRequest(reqType,
										 reqProductosCantidades,
										 reqOrdenCompra,
										 reqProducto,
										 reqCantidadMinima);

		switch(reqType)
		{
		case middleware::CommunicationAdpt::RESERVAR:
		case middleware::CommunicationAdpt::FIN_RESERVA:
		{
			ss.str("");
			CLogger::log("Se recibio request de reserva", LogLevelEnum::TRACE);


			application::Adpt::EstanteNumList estanteList;
			application::Adpt::EstanteNumList::iterator estanteListIt;
			std::map<TipoProducto,application::Adpt::EstanteNumList>
							estantesForProducto;
			std::map<TipoProducto,application::Adpt::EstanteNumList>::iterator
					itEstantesForProducto;

			std::map<TipoProducto,int> cantidadesForProducto;
			std::map<TipoProducto,int>::iterator itCantidadesForProducto;

			ProductoCantidadMap cantidadesMinimas;

			int itemsRestantes;
			int cantProductosReservar;
			int espacioTotalNecesario=0;

			//Voy tomando mensajes hasta recibir el mensaje de fin
			while(reqType != middleware::CommunicationAdpt::FIN_RESERVA)
			{
				cantidadesMinimas[reqProductosCantidades.tipo] = reqCantidadMinima;
				ss.str("");
				ss <<"Obtenido producto:"<<reqProductosCantidades.tipo
				   <<" cantidad:"<<reqProductosCantidades.cantidad;

				CLogger::log(ss.str(), LogLevelEnum::TRACE);

				cantidadesForProducto[reqProductosCantidades.tipo]+=
											reqProductosCantidades.cantidad;

				itEstantesForProducto = estantesForProducto.find(reqProductosCantidades.tipo);

				//Obtengo el siguiente mensaje
				communicationAdpt.getAdptRequest(reqType,
												 reqProductosCantidades,
												 reqOrdenCompra,
												 reqProducto,
												 reqCantidadMinima);
			}

			//Busco el stock para cada producto del pedido
			for(itCantidadesForProducto=cantidadesForProducto.begin();
					itCantidadesForProducto!=cantidadesForProducto.end();
					itCantidadesForProducto++)
			{
				int cantidadNecesariaProducto;
				estanteList = almacen.buscarProductoSinOC(itCantidadesForProducto->first);

				//Si no hay suficientes verifico que haya espacio para
				//Los nuevos productos.
				if(estanteList.size()< itCantidadesForProducto->second)
				{
					cantidadNecesariaProducto = itCantidadesForProducto->second - estanteList.size();

					std::cout << "prod:"<<cantidadNecesariaProducto<<"min:"<<cantidadesMinimas[itCantidadesForProducto->first]<<std::endl;

					if(cantidadNecesariaProducto <cantidadesMinimas[itCantidadesForProducto->first])
					{
						ss.str("");
						ss <<"La cantidad pedida para el producto:"<<itCantidadesForProducto->first
						   <<"es muy baja, se manda a producir el minimo:"<<cantidadesMinimas[itCantidadesForProducto->first];

						CLogger::log(ss.str(), LogLevelEnum::TRACE);

						espacioTotalNecesario += cantidadesMinimas[itCantidadesForProducto->first];
					}
					else
						espacioTotalNecesario += cantidadNecesariaProducto;

					ss.str("");
					ss <<"No hay stock suficiente para el producto de tipo:"<<itCantidadesForProducto->first;
					CLogger::log(ss.str(), LogLevelEnum::TRACE);
				}

				estantesForProducto[itCantidadesForProducto->first] =estanteList;
			}

			//Si hay espacio
			if(espacioTotalNecesario <= almacen.obtenerCantEstantesVacios())
			{
				for(itEstantesForProducto = estantesForProducto.begin();
					itEstantesForProducto != estantesForProducto.end();
					itEstantesForProducto++)

				{
					estanteList = itEstantesForProducto->second;

					almacen.asignarOCEstantes(estanteList,
											  reqOrdenCompra);

					cantProductosReservar = cantidadesForProducto[itEstantesForProducto->first]
					                        - estanteList.size();

					//Si hay productos sin producir
					if(cantProductosReservar >0)
					{
						ProductoCantidad productosReservar;
						application::Adpt::EstanteNumList estanteForNewProductsList;

						ss.str("");
						ss <<"La cantidad minima de productos de tipo:"<<itEstantesForProducto->first
						   <<" es:"<<cantidadesMinimas[itEstantesForProducto->first];
						CLogger::log(ss.str(), LogLevelEnum::TRACE);

						productosReservar.tipo = itEstantesForProducto->first;
						productosReservar.cantidad = cantProductosReservar;

						//Reservo estantes
						almacen.reservarEstantesParaProducto(productosReservar,estanteForNewProductsList);

						//Asigno orden de compra
						almacen.asignarOCEstantes(estanteForNewProductsList,
												  reqOrdenCompra);

						if(cantProductosReservar < cantidadesMinimas[productosReservar.tipo])
						{
							productosReservar.tipo = itEstantesForProducto->first;
							productosReservar.cantidad = cantidadesMinimas[productosReservar.tipo] - cantProductosReservar;

							ss.str("");
							ss <<"Se reservan:"<<productosReservar.cantidad<<" estantes adicionales para el producto:"<<productosReservar.tipo;
							CLogger::log(ss.str(), LogLevelEnum::TRACE);

							//Reservo estantes
							almacen.reservarEstantesParaProducto(productosReservar,estanteForNewProductsList);
						}
					}

					//Se mandan la cantidad de productos en stock
					ProductoCantidad productosEnStock;
					productosEnStock.tipo = itEstantesForProducto->first;
					productosEnStock.cantidad = cantidadesForProducto[itEstantesForProducto->first]- cantProductosReservar;

					communicationAdpt.responderVendedor(middleware::CommunicationAdpt::RESPUESTA_RESERVA,
													    reqOrdenCompra.vendedorId,
													    productosEnStock);

					//Si se completo el pedido para este producto, aviso a despacho
					if(almacen.getCantidadProductosRestantesOC(itEstantesForProducto->first, reqOrdenCompra)==0)
						communicationDespacho.notificarDespachoOCLista(reqOrdenCompra,productosEnStock);
				}

				std::cout << "Almacen serializado"<<std::endl<< almacen.serialize()<<std::endl;

				//Fin de reservas
				communicationAdpt.responderVendedor(middleware::CommunicationAdpt::FIN_RESPUESTA_RESERVA,
													reqOrdenCompra.vendedorId);

				CLogger::log("Fin de reserva", LogLevelEnum::TRACE);

			}
			//Si no hay espacio
			else
			{
				CLogger::log("No hay suficiente espacio en el almacen", LogLevelEnum::ERROR);
				communicationAdpt.responderVendedor(middleware::CommunicationAdpt::NO_HAY_ESPACIO,
												    reqOrdenCompra.vendedorId);
			}

			break;
		}
		case middleware::CommunicationAdpt::INSERTAR:
		{
			int estanteId;
			OrdenCompraHeader ordenCompraEstante;
			ProductoCantidad productoCantidad;
			ss.str("");
			CLogger::log("Se recibio request de insercion de un producto", LogLevelEnum::TRACE);

			estanteId = almacen.obtenerEstanteProducto(reqProducto.tipo);

			ss << "Se ingresara un producto:"<< reqProducto.productoId
			   << " de tipo:"<< reqProducto.tipo
			   << " en el ADPT en el estante:" << estanteId;

			CLogger::log(ss.str(), LogLevelEnum::TRACE);

			almacen.almacenarProducto(estanteId,reqProducto);

			almacen.getOrdenCompraEstante(estanteId,ordenCompraEstante);
			almacen.getProductoCantidad(ordenCompraEstante,productoCantidad);

			if(almacen.getCantidadProductosRestantesOC(reqProducto.tipo, ordenCompraEstante)==0)
				communicationDespacho.notificarDespachoOCLista(ordenCompraEstante,productoCantidad);

			std::cout << "Almacen serializado"<<std::endl<< almacen.serialize()<<std::endl;

			break;
		}
		case middleware::CommunicationAdpt::RETIRAR:
		{
			ss.str("");
			Producto productoObtenido;
			CLogger::log("Se recibio request de retiro de un producto", LogLevelEnum::TRACE);

			application::Adpt::EstanteNumList estanteList;

			estanteList = almacen.buscarProductoConOC(reqOrdenCompra.ordenCompraId,
													  reqProductosCantidades.tipo);

			//Si no se encontro el producto con la orden de compra.
			if(estanteList.size() == 0)
			{
				ss.str("");
				ss << "No estaba reservado el producto:"<<reqProductosCantidades.tipo
				   << " para la orden de compra:"<<reqOrdenCompra.ordenCompraId;

				CLogger::log(ss.str(), LogLevelEnum::WARN);

				//Busco si hay stock sin reservar como para cubrir el pedido.
				estanteList = almacen.buscarProductoSinOC(reqProductosCantidades.tipo);
			}

			if(estanteList.size()==0)
			{
				ss.str("");
				ss <<"No habia stock del producto:"<<reqProductosCantidades.tipo;

				CLogger::log(ss.str(), LogLevelEnum::WARN);
			}
			else
			{
				//Devuelvo el producto al robot16
				almacen.retirarProductoEstante(estanteList.front(),productoObtenido);

				communicationAdpt.entregarProducto(productoObtenido);
			}
			std::cout << "Almacen serializado"<<std::endl<< almacen.serialize()<<std::endl;

			break;
		}
		default:
			CLogger::log("Se recibio request de tipo desconocido", LogLevelEnum::ERROR);
			break;
		}
	}

	CLogger::log("Almacen productos terminados ended", LogLevelEnum::INFO);

	return 0;
}
